"""本地文件操作模块

提供本地文件系统的操作功能，包括文件列表、复制、移动、删除等操作。
可以指定文件目录地址进行操作。
"""

import os
import shutil
import hashlib
import json
from typing import Dict, Any, Optional, List
from datetime import datetime
import stat


class LocalFileOperations:
    """本地文件操作类"""
    
    def __init__(self, base_path: str = None):
        """初始化本地文件操作
        
        Args:
            base_path: 基础路径，如果不指定则使用当前工作目录
        """
        self.base_path = base_path or os.getcwd()
        if not os.path.exists(self.base_path):
            os.makedirs(self.base_path, exist_ok=True)
    
    def set_base_path(self, path: str) -> Dict[str, Any]:
        """设置基础路径
        
        Args:
            path: 新的基础路径
            
        Returns:
            设置结果
        """
        try:
            if not os.path.exists(path):
                return {"success": False, "error": "路径不存在"}
            
            if not os.path.isdir(path):
                return {"success": False, "error": "路径不是目录"}
            
            self.base_path = os.path.abspath(path)
            return {
                "success": True,
                "message": "基础路径设置成功",
                "base_path": self.base_path
            }
            
        except Exception as e:
            return {"success": False, "error": f"设置基础路径异常: {str(e)}"}
    
    def get_base_path(self) -> str:
        """获取当前基础路径
        
        Returns:
            当前基础路径
        """
        return self.base_path
    
    def _get_full_path(self, relative_path: str) -> str:
        """获取完整路径
        
        Args:
            relative_path: 相对路径
            
        Returns:
            完整路径
        """
        if os.path.isabs(relative_path):
            return relative_path
        return os.path.join(self.base_path, relative_path)
    
    def list_files(self, directory: str = ".", show_hidden: bool = False) -> Dict[str, Any]:
        """列出目录中的文件和文件夹
        
        Args:
            directory: 目录路径，相对于基础路径
            show_hidden: 是否显示隐藏文件
            
        Returns:
            文件列表信息
        """
        try:
            full_path = self._get_full_path(directory)
            
            if not os.path.exists(full_path):
                return {"success": False, "error": "目录不存在"}
            
            if not os.path.isdir(full_path):
                return {"success": False, "error": "路径不是目录"}
            
            files = []
            
            for item in os.listdir(full_path):
                if not show_hidden and item.startswith('.'):
                    continue
                
                item_path = os.path.join(full_path, item)
                stat_info = os.stat(item_path)
                
                file_info = {
                    "name": item,
                    "path": os.path.relpath(item_path, self.base_path),
                    "full_path": item_path,
                    "type": "directory" if os.path.isdir(item_path) else "file",
                    "size": stat_info.st_size,
                    "create_time": datetime.fromtimestamp(stat_info.st_ctime).isoformat(),
                    "modify_time": datetime.fromtimestamp(stat_info.st_mtime).isoformat(),
                    "access_time": datetime.fromtimestamp(stat_info.st_atime).isoformat(),
                    "permissions": oct(stat_info.st_mode)[-3:],
                    "is_readable": os.access(item_path, os.R_OK),
                    "is_writable": os.access(item_path, os.W_OK),
                    "is_executable": os.access(item_path, os.X_OK)
                }
                
                # 如果是文件，计算MD5
                if os.path.isfile(item_path):
                    try:
                        file_info["md5"] = self.calculate_md5(item_path)
                    except:
                        file_info["md5"] = None
                
                files.append(file_info)
            
            # 按类型和名称排序
            files.sort(key=lambda x: (x["type"] == "file", x["name"].lower()))
            
            return {
                "success": True,
                "directory": directory,
                "full_path": full_path,
                "files": files,
                "total": len(files)
            }
            
        except Exception as e:
            return {"success": False, "error": f"列出文件异常: {str(e)}"}
    
    def get_file_info(self, file_path: str) -> Dict[str, Any]:
        """获取文件或目录信息
        
        Args:
            file_path: 文件路径，相对于基础路径
            
        Returns:
            文件信息
        """
        try:
            full_path = self._get_full_path(file_path)
            
            if not os.path.exists(full_path):
                return {"success": False, "error": "文件或目录不存在"}
            
            stat_info = os.stat(full_path)
            
            file_info = {
                "name": os.path.basename(full_path),
                "path": os.path.relpath(full_path, self.base_path),
                "full_path": full_path,
                "type": "directory" if os.path.isdir(full_path) else "file",
                "size": stat_info.st_size,
                "create_time": datetime.fromtimestamp(stat_info.st_ctime).isoformat(),
                "modify_time": datetime.fromtimestamp(stat_info.st_mtime).isoformat(),
                "access_time": datetime.fromtimestamp(stat_info.st_atime).isoformat(),
                "permissions": oct(stat_info.st_mode)[-3:],
                "is_readable": os.access(full_path, os.R_OK),
                "is_writable": os.access(full_path, os.W_OK),
                "is_executable": os.access(full_path, os.X_OK)
            }
            
            # 如果是文件，计算MD5和获取扩展名
            if os.path.isfile(full_path):
                try:
                    file_info["md5"] = self.calculate_md5(full_path)
                    file_info["extension"] = os.path.splitext(full_path)[1].lower()
                except:
                    file_info["md5"] = None
                    file_info["extension"] = None
            
            return {
                "success": True,
                "file_info": file_info
            }
            
        except Exception as e:
            return {"success": False, "error": f"获取文件信息异常: {str(e)}"}
    
    def create_directory(self, directory_path: str) -> Dict[str, Any]:
        """创建目录
        
        Args:
            directory_path: 目录路径，相对于基础路径
            
        Returns:
            创建结果
        """
        try:
            full_path = self._get_full_path(directory_path)
            
            if os.path.exists(full_path):
                return {"success": False, "error": "目录已存在"}
            
            os.makedirs(full_path, exist_ok=True)
            
            return {
                "success": True,
                "message": "目录创建成功",
                "path": directory_path,
                "full_path": full_path
            }
            
        except Exception as e:
            return {"success": False, "error": f"创建目录异常: {str(e)}"}
    
    def copy_file(self, source_path: str, destination_path: str, overwrite: bool = False) -> Dict[str, Any]:
        """复制文件或目录
        
        Args:
            source_path: 源路径，相对于基础路径
            destination_path: 目标路径，相对于基础路径
            overwrite: 是否覆盖已存在的文件
            
        Returns:
            复制结果
        """
        try:
            source_full = self._get_full_path(source_path)
            dest_full = self._get_full_path(destination_path)
            
            if not os.path.exists(source_full):
                return {"success": False, "error": "源文件或目录不存在"}
            
            if os.path.exists(dest_full) and not overwrite:
                return {"success": False, "error": "目标文件已存在，且未设置覆盖"}
            
            # 创建目标目录
            dest_dir = os.path.dirname(dest_full)
            if not os.path.exists(dest_dir):
                os.makedirs(dest_dir, exist_ok=True)
            
            if os.path.isfile(source_full):
                shutil.copy2(source_full, dest_full)
                operation = "文件复制成功"
            else:
                if os.path.exists(dest_full):
                    shutil.rmtree(dest_full)
                shutil.copytree(source_full, dest_full)
                operation = "目录复制成功"
            
            return {
                "success": True,
                "message": operation,
                "source": source_path,
                "destination": destination_path
            }
            
        except Exception as e:
            return {"success": False, "error": f"复制异常: {str(e)}"}
    
    def move_file(self, source_path: str, destination_path: str, overwrite: bool = False) -> Dict[str, Any]:
        """移动文件或目录
        
        Args:
            source_path: 源路径，相对于基础路径
            destination_path: 目标路径，相对于基础路径
            overwrite: 是否覆盖已存在的文件
            
        Returns:
            移动结果
        """
        try:
            source_full = self._get_full_path(source_path)
            dest_full = self._get_full_path(destination_path)
            
            if not os.path.exists(source_full):
                return {"success": False, "error": "源文件或目录不存在"}
            
            if os.path.exists(dest_full) and not overwrite:
                return {"success": False, "error": "目标文件已存在，且未设置覆盖"}
            
            # 创建目标目录
            dest_dir = os.path.dirname(dest_full)
            if not os.path.exists(dest_dir):
                os.makedirs(dest_dir, exist_ok=True)
            
            # 如果目标存在且允许覆盖，先删除
            if os.path.exists(dest_full) and overwrite:
                if os.path.isfile(dest_full):
                    os.remove(dest_full)
                else:
                    shutil.rmtree(dest_full)
            
            shutil.move(source_full, dest_full)
            
            return {
                "success": True,
                "message": "移动成功",
                "source": source_path,
                "destination": destination_path
            }
            
        except Exception as e:
            return {"success": False, "error": f"移动异常: {str(e)}"}
    
    def delete_file(self, file_path: str, force: bool = False) -> Dict[str, Any]:
        """删除文件或目录
        
        Args:
            file_path: 文件路径，相对于基础路径
            force: 是否强制删除（对于目录）
            
        Returns:
            删除结果
        """
        try:
            full_path = self._get_full_path(file_path)
            
            if not os.path.exists(full_path):
                return {"success": False, "error": "文件或目录不存在"}
            
            if os.path.isfile(full_path):
                os.remove(full_path)
                operation = "文件删除成功"
            else:
                if not force and os.listdir(full_path):
                    return {"success": False, "error": "目录不为空，请使用force=True强制删除"}
                shutil.rmtree(full_path)
                operation = "目录删除成功"
            
            return {
                "success": True,
                "message": operation,
                "path": file_path
            }
            
        except Exception as e:
            return {"success": False, "error": f"删除异常: {str(e)}"}
    
    def rename_file(self, old_path: str, new_name: str) -> Dict[str, Any]:
        """重命名文件或目录
        
        Args:
            old_path: 原路径，相对于基础路径
            new_name: 新名称
            
        Returns:
            重命名结果
        """
        try:
            old_full = self._get_full_path(old_path)
            
            if not os.path.exists(old_full):
                return {"success": False, "error": "文件或目录不存在"}
            
            old_dir = os.path.dirname(old_full)
            new_full = os.path.join(old_dir, new_name)
            
            if os.path.exists(new_full):
                return {"success": False, "error": "新名称已存在"}
            
            os.rename(old_full, new_full)
            
            new_relative = os.path.relpath(new_full, self.base_path)
            
            return {
                "success": True,
                "message": "重命名成功",
                "old_path": old_path,
                "new_path": new_relative,
                "new_name": new_name
            }
            
        except Exception as e:
            return {"success": False, "error": f"重命名异常: {str(e)}"}
    
    def calculate_md5(self, file_path: str) -> str:
        """计算文件MD5值
        
        Args:
            file_path: 文件路径
            
        Returns:
            文件的MD5值
        """
        if not os.path.isabs(file_path):
            file_path = self._get_full_path(file_path)
        
        md5_hash = hashlib.md5()
        with open(file_path, 'rb') as f:
            for chunk in iter(lambda: f.read(4096), b""):
                md5_hash.update(chunk)
        return md5_hash.hexdigest()
    
    def calculate_sha1(self, file_path: str) -> str:
        """计算文件SHA1值
        
        Args:
            file_path: 文件路径
            
        Returns:
            文件的SHA1值
        """
        if not os.path.isabs(file_path):
            file_path = self._get_full_path(file_path)
        
        sha1_hash = hashlib.sha1()
        with open(file_path, 'rb') as f:
            for chunk in iter(lambda: f.read(4096), b""):
                sha1_hash.update(chunk)
        return sha1_hash.hexdigest()
    
    def get_directory_size(self, directory_path: str = ".") -> Dict[str, Any]:
        """获取目录大小
        
        Args:
            directory_path: 目录路径，相对于基础路径
            
        Returns:
            目录大小信息
        """
        try:
            full_path = self._get_full_path(directory_path)
            
            if not os.path.exists(full_path):
                return {"success": False, "error": "目录不存在"}
            
            if not os.path.isdir(full_path):
                return {"success": False, "error": "路径不是目录"}
            
            total_size = 0
            file_count = 0
            dir_count = 0
            
            for dirpath, dirnames, filenames in os.walk(full_path):
                dir_count += len(dirnames)
                for filename in filenames:
                    file_path = os.path.join(dirpath, filename)
                    try:
                        total_size += os.path.getsize(file_path)
                        file_count += 1
                    except (OSError, IOError):
                        # 跳过无法访问的文件
                        pass
            
            return {
                "success": True,
                "directory": directory_path,
                "total_size": total_size,
                "total_size_mb": round(total_size / (1024 * 1024), 2),
                "file_count": file_count,
                "directory_count": dir_count
            }
            
        except Exception as e:
            return {"success": False, "error": f"获取目录大小异常: {str(e)}"}
    
    def search_files(self, pattern: str, directory: str = ".", case_sensitive: bool = False) -> Dict[str, Any]:
        """搜索文件
        
        Args:
            pattern: 搜索模式（支持通配符）
            directory: 搜索目录，相对于基础路径
            case_sensitive: 是否区分大小写
            
        Returns:
            搜索结果
        """
        try:
            import fnmatch
            
            full_path = self._get_full_path(directory)
            
            if not os.path.exists(full_path):
                return {"success": False, "error": "搜索目录不存在"}
            
            if not os.path.isdir(full_path):
                return {"success": False, "error": "搜索路径不是目录"}
            
            matches = []
            
            for root, dirs, files in os.walk(full_path):
                for name in files + dirs:
                    if case_sensitive:
                        if fnmatch.fnmatch(name, pattern):
                            full_match_path = os.path.join(root, name)
                            relative_path = os.path.relpath(full_match_path, self.base_path)
                            matches.append({
                                "name": name,
                                "path": relative_path,
                                "full_path": full_match_path,
                                "type": "directory" if os.path.isdir(full_match_path) else "file"
                            })
                    else:
                        if fnmatch.fnmatch(name.lower(), pattern.lower()):
                            full_match_path = os.path.join(root, name)
                            relative_path = os.path.relpath(full_match_path, self.base_path)
                            matches.append({
                                "name": name,
                                "path": relative_path,
                                "full_path": full_match_path,
                                "type": "directory" if os.path.isdir(full_match_path) else "file"
                            })
            
            return {
                "success": True,
                "pattern": pattern,
                "directory": directory,
                "matches": matches,
                "count": len(matches)
            }
            
        except Exception as e:
            return {"success": False, "error": f"搜索文件异常: {str(e)}"}
    
    def create_file(self, file_path: str, content: str = "", encoding: str = "utf-8") -> Dict[str, Any]:
        """创建文件
        
        Args:
            file_path: 文件路径，相对于基础路径
            content: 文件内容
            encoding: 文件编码
            
        Returns:
            创建结果
        """
        try:
            full_path = self._get_full_path(file_path)
            
            if os.path.exists(full_path):
                return {"success": False, "error": "文件已存在"}
            
            # 创建目录
            file_dir = os.path.dirname(full_path)
            if not os.path.exists(file_dir):
                os.makedirs(file_dir, exist_ok=True)
            
            with open(full_path, 'w', encoding=encoding) as f:
                f.write(content)
            
            return {
                "success": True,
                "message": "文件创建成功",
                "path": file_path,
                "full_path": full_path,
                "size": len(content.encode(encoding))
            }
            
        except Exception as e:
            return {"success": False, "error": f"创建文件异常: {str(e)}"}
    
    def read_file(self, file_path: str, encoding: str = "utf-8") -> Dict[str, Any]:
        """读取文件内容
        
        Args:
            file_path: 文件路径，相对于基础路径
            encoding: 文件编码
            
        Returns:
            文件内容
        """
        try:
            full_path = self._get_full_path(file_path)
            
            if not os.path.exists(full_path):
                return {"success": False, "error": "文件不存在"}
            
            if not os.path.isfile(full_path):
                return {"success": False, "error": "路径不是文件"}
            
            with open(full_path, 'r', encoding=encoding) as f:
                content = f.read()
            
            return {
                "success": True,
                "path": file_path,
                "content": content,
                "size": len(content.encode(encoding))
            }
            
        except Exception as e:
            return {"success": False, "error": f"读取文件异常: {str(e)}"}
    
    def write_file(self, file_path: str, content: str, encoding: str = "utf-8", overwrite: bool = False) -> Dict[str, Any]:
        """写入文件内容
        
        Args:
            file_path: 文件路径，相对于基础路径
            content: 文件内容
            encoding: 文件编码
            overwrite: 是否覆盖已存在的文件
            
        Returns:
            写入结果
        """
        try:
            full_path = self._get_full_path(file_path)
            
            if os.path.exists(full_path) and not overwrite:
                return {"success": False, "error": "文件已存在，且未设置覆盖"}
            
            # 创建目录
            file_dir = os.path.dirname(full_path)
            if not os.path.exists(file_dir):
                os.makedirs(file_dir, exist_ok=True)
            
            with open(full_path, 'w', encoding=encoding) as f:
                f.write(content)
            
            return {
                "success": True,
                "message": "文件写入成功",
                "path": file_path,
                "full_path": full_path,
                "size": len(content.encode(encoding))
            }
            
        except Exception as e:
            return {"success": False, "error": f"写入文件异常: {str(e)}"}